package org.ns.framework.datasource.mybatis;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.ns.framework.datasource.anns.DBSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
/**
 * MyBatis 数据源注入
 * 
 * @author zhangxingyu
 *
 */
public class DataSourceServiceInjector implements BeanPostProcessor , ApplicationContextAware {
	
	private static final Logger log = LoggerFactory.getLogger(DataSourceServiceInjector.class);

    private ApplicationContext applicationContext;
    @Autowired(required = false)
    Boolean ignoreInjectorPrefixVerify = false;
    @Autowired(required = false)
    MybatisMapperManage mybatisMapperManage;
    
    private Map<String, DataSourceService> dbServiceMap = new ConcurrentHashMap<>();
    
    public DataSourceService getDataSourceService(String datasourceName, Class<?> dbServiceType) {
    	if (applicationContext == null) {
    		return null;
    	}
    	if (!DataSourceService.class.isAssignableFrom(dbServiceType)) {
    		return null;
    	}
    	try {
	    	String dbServiceName = dbServiceType.getSimpleName() + "_" + datasourceName;
	    	BeanDefinitionRegistry registry = (BeanDefinitionRegistry)applicationContext;
	    	DataSourceService dataSourceService = dbServiceMap.get(dbServiceName);
			if (dataSourceService == null) {
				synchronized (dbServiceMap) {
					if (dataSourceService == null) {
						BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(dbServiceType);
						BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
						registry.registerBeanDefinition(dbServiceName, beanDefinition);
						dataSourceService = applicationContext.getBean(dbServiceName, DataSourceService.class);
						dbServiceMap.put(dbServiceName, dataSourceService);
					}
				}
			}
			return dataSourceService;
    	} catch (Exception e) {
    		log.error("", e);
    		return null;
		}
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        String name = targetClass.getName();
        if (ignoreInjectorPrefixVerify == null || !ignoreInjectorPrefixVerify) {
	        // 不执行框架内的, 就是不给你用!.
	        if (name.startsWith("org.apache.") || name.startsWith("org.springframework.")) {
	        	return bean;
	        }
        }
        List<Field> fields = findFields(targetClass);
        if (fields.size() == 0) { // 必须存在DBSource标记的字段才可以继续下去.
        	return bean;
        }
        for (Field field : fields) {
        	Class<?> dbServiceType = field.getType(); // imp DataSourceService
        	List<Field> dbMappers = findDataSourceFields(dbServiceType);
        	if (dbMappers.size() == 0) {
        		continue;
        	}
        	DBSource dbSource = AnnotatedElementUtils.getMergedAnnotation(field, DBSource.class);
        	String datasourceName = dbSource.value();
			DataSourceService dataSourceService = getDataSourceService(datasourceName, dbServiceType);
			if (dataSourceService == null) {
				continue;
			}
			for (Field dbMapper : dbMappers) {
				injector(dbMapper, dataSourceService, datasourceName);
			}
			try {
				field.setAccessible(true);
				field.set(bean, dataSourceService);
			} catch (Exception e) {
				throw new BeanInitializationException("DBSource injector create fail - 0", e);
			}
        }
        return bean;
    }

    private void injector(Field field, Object bean, String datasourceName) throws BeansException {
        Class<?> fieldType = field.getType();

        Object value = mybatisMapperManage.getMapper(datasourceName, fieldType);
        
        if (value != null) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                field.set(bean, value);
            } catch (Exception e) {
                throw new BeanInitializationException("DBSource injector create fail - 1", e);
            }
        }
    }
    
    
    private List<Field> findFields(Class<?> targetClass) {
        List<Field> fields = new ArrayList<>();
        Class<?> nextClass = targetClass;
        while (nextClass != null && nextClass != Object.class) {
            Field[] declaredFields = nextClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> fieldType = declaredField.getType();
                if (AnnotatedElementUtils.hasAnnotation(declaredField, DBSource.class)
                	&& !declaredField.isAnnotationPresent(Autowired.class)
                    && !fieldType.isInterface()
                    && DataSourceService.class.isAssignableFrom(fieldType)
                ) {
                    fields.add(declaredField);
                }
            }
            nextClass = nextClass.getSuperclass();
        }
        return fields;
    }


    private List<Field> findDataSourceFields (Class<?> targetClass) {

        List<Field> fields = new ArrayList<>();
        Class<?> nextClass = targetClass;
        while (nextClass != null && nextClass != Object.class) {
            Field[] declaredFields = nextClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> fieldType = declaredField.getType();
                if (!AnnotatedElementUtils.hasAnnotation(declaredField, DBSource.class) // 没有被DBSource修饰
                    && (fieldType.isInterface() && DataSourceService.class.isAssignableFrom(fieldType) // 不是接口并且是DataSourceService
                        || fieldType == SqlSessionFactory.class // SqlSessionFactory
                        || fieldType == DataSource.class  // DataSource
                        )
                ) {
                    fields.add(declaredField);
                }
            }

            nextClass = nextClass.getSuperclass();
        }
        return fields;
    }

    
//	private List<Method> findMethods(Class<?> targetClass) {
//		List<Method> methods = new ArrayList<>();
//		Class<?> nextClass = targetClass;
//		while (nextClass != null && nextClass != Object.class) {
//			Method[] declaredMethods = nextClass.getDeclaredMethods();
//			for (Method declaredMethod : declaredMethods) {
//				int mod = declaredMethod.getModifiers();
//				if (AnnotatedElementUtils.hasMetaAnnotationTypes(declaredMethod,
//						"org.springframework.transaction.annotation.Transactional") && !Modifier.isAbstract(mod)) {
//					methods.add(declaredMethod);
//				}
//			}
//			nextClass = nextClass.getSuperclass();
//		}
//		return methods;
//	}
	
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
